#include "InputSequence.h"

#define CHAR_BITS_A 0UL
#define CHAR_BITS_C 1UL
#define CHAR_BITS_G 2UL
#define CHAR_BITS_T 3UL

const unsigned int InputSequence::CHARS_PER_BLOCK = (sizeof(Block) << 2);

/*
	The sequence will be held in a bits vector. Every 2 bits will be a single character.
	In each block the data will be held reversed (to ease operations).
*/
InputSequence::InputSequence() : 
	length(0), 
	pairs(),
	seqName()
{
	pairs.reserve(4096);
}

InputSequence::~InputSequence()
{
}

#include <iostream>

void InputSequence::append(const char *arr, const uint arrLen)
{
	uint readCount = 0;

	/* Check if we need to fill the last element */
	uint lastTaken = length % CHARS_PER_BLOCK; // The number of taken spots in the last block
	if (lastTaken != 0)
	{
		ulong ref = pairs[pairs.size() - 1];
		do {
			ulong toAdd;
			switch (arr[readCount])
			{
				case 'A':
					toAdd = CHAR_BITS_A;
					break;
				case 'C':
					toAdd = CHAR_BITS_C;
					break;
				case 'G':
					toAdd = CHAR_BITS_G;
					break;
				case 'T':
					toAdd = CHAR_BITS_T;
					break;
				default:
					/* Error - should not reach here */
					toAdd = 0xFFFFFFFFUL;
					cout << "ERROR!!!!!!!!!" << endl;
					break;

			}
			ref |= (toAdd << (lastTaken << 1)); 
			readCount++;
			lastTaken++;
		} while ((lastTaken != CHARS_PER_BLOCK) && (readCount < arrLen));
		pairs[pairs.size() - 1] = ref;
	}

	for (; readCount < arrLen;)
	{
		/* Add as many chars as can (up to CHARS_PER_BLOCK chars) into the block */
		ulong newBlock = 0;
		int shift = 0;
		for (uint i = 0; (i < CHARS_PER_BLOCK) && (readCount < arrLen); i++)
		{
			switch (arr[readCount])
			{
				case 'A':
					newBlock |= (CHAR_BITS_A << shift);
					break;
				case 'C':
					newBlock |= (CHAR_BITS_C << shift);
					break;
				case 'G':
					newBlock |= (CHAR_BITS_G << shift);
					break;
				case 'T':
					newBlock |= (CHAR_BITS_T << shift);
					break;
				default:
					/* Error - should not reach here */
					break;

			}
			shift += 2;
			readCount++;
		}

		pairs.push_back(newBlock);
	}
	length += readCount;
}

size_t InputSequence::getLength() const
{
	return length;
}

void InputSequence::setName(string& name)
{
	seqName = name;
}

string InputSequence::getName() const
{
	return seqName;
}

/* 
	This method returns the actual charachter at 'index'. 
	Should be used for debug purpuses only.
*/

char InputSequence::getChar(const size_t index) const
{
	ulong word = pairs[index / CHARS_PER_BLOCK];
	int shift = ((index % CHARS_PER_BLOCK) << 1);
	ulong mask = 3UL << shift;
	switch ((word & mask) >> shift)
	{
		case 0:
			return 'A';
			break;
		case 1:
			return 'C';
			break;
		case 2:
			return 'G';
			break;
		case 3:
			return 'T';
			break;
		default:
			/* Error - should not reach here */
			return '\0';
			break;
	}
}

short InputSequence::getCharAsShort(const size_t index) const
{
	ulong word = pairs[index / CHARS_PER_BLOCK];
	int shift = ((index % CHARS_PER_BLOCK) << 1);
	ulong mask = 3UL << shift;
	return (short)((word & mask) >> shift);
}

#ifdef DEBUG
#include <iostream>

void InputSequence::print() const
{
	cout << "Sequence " << seqName << ":" << endl;
	for (ulong i = 0; i < length; i++)
	{
		ulong word = pairs[i / CHARS_PER_BLOCK];
		int shift = ((i % CHARS_PER_BLOCK) << 1);
		ulong mask = 3UL << shift;
		switch ((word & mask) >> shift)
		{
			case 0:
				cout << 'A';
				break;
			case 1:
				cout << 'C';
				break;
			case 2:
				cout << 'G';
				break;
			case 3:
				cout << 'T';
				break;
			default:
				/* Error - should not reach here */
				break;
		}
		if ((i + 1) % 81 == 0)
			cout << endl;
	}
	cout << endl;
}


#endif /* DEBUG */

